package com.tsfyun.scm.service.impl.order;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.tsfyun.common.base.dto.*;
import com.tsfyun.common.base.enums.BaseDataTypeEnum;
import com.tsfyun.common.base.enums.DistributedLockEnum;
import com.tsfyun.common.base.enums.TransactionModeEnum;
import com.tsfyun.common.base.enums.domain.*;
import com.tsfyun.common.base.enums.exp.CollectionSourceEnum;
import com.tsfyun.common.base.enums.finance.TransactionCategoryEnum;
import com.tsfyun.common.base.enums.finance.TransactionTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.help.excel.ExcelUtil;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.*;
import com.tsfyun.common.base.validator.ValidatorUtils;
import com.tsfyun.common.base.validator.group.AddGroup;
import com.tsfyun.common.base.validator.group.UpdateGroup;
import com.tsfyun.scm.dto.finance.ClaimedAmountOrderDTO;
import com.tsfyun.scm.dto.finance.ClaimedAmountPlusDTO;
import com.tsfyun.scm.dto.order.*;
import com.tsfyun.scm.dto.support.TaskNoticeContentDTO;
import com.tsfyun.scm.entity.base.Currency;
import com.tsfyun.scm.entity.customer.Customer;
import com.tsfyun.scm.entity.customer.ExpAgreement;
import com.tsfyun.scm.entity.customer.Supplier;
import com.tsfyun.scm.entity.customs.Declaration;
import com.tsfyun.scm.entity.finance.OverseasReceivingOrder;
import com.tsfyun.scm.entity.finance.PurchaseContract;
import com.tsfyun.scm.entity.order.*;
import com.tsfyun.scm.entity.system.SubjectOverseas;
import com.tsfyun.scm.enums.DeliveryDestinationEnum;
import com.tsfyun.scm.excel.ExpOrderMemberExcel;
import com.tsfyun.scm.mapper.order.ExpOrderMapper;
import com.tsfyun.scm.service.base.ICurrencyService;
import com.tsfyun.scm.service.base.ISystemCacheService;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.customer.ICustomerService;
import com.tsfyun.scm.service.customer.IExpAgreementService;
import com.tsfyun.scm.service.customer.ISupplierService;
import com.tsfyun.scm.service.customs.IDeclarationService;
import com.tsfyun.scm.service.file.IUploadFileService;
import com.tsfyun.scm.service.finance.*;
import com.tsfyun.scm.service.order.*;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import com.tsfyun.scm.service.system.ISubjectOverseasService;
import com.tsfyun.scm.service.user.IPersonService;
import com.tsfyun.scm.stream.send.ScmProcessor;
import com.tsfyun.scm.system.vo.BaseDataVO;
import com.tsfyun.scm.util.TsfWeekendSqls;
import com.tsfyun.scm.vo.finance.OverseasReceivingOrderVO;
import com.tsfyun.scm.vo.order.*;
import com.tsfyun.scm.vo.user.SimplePersonInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * <p>
 * 出口订单 服务实现类
 * </p>
 *
 *
 * @since 2021-01-26
 */
@Slf4j
@Service
public class ExpOrderServiceImpl extends ServiceImpl<ExpOrder> implements IExpOrderService {

    @Autowired
    private ICustomerService customerService;
    @Autowired
    private IExpAgreementService expAgreementService;
    @Autowired
    private ICurrencyService currencyService;
    @Autowired
    private IExpOrderMemberService expOrderMemberService;
    @Autowired
    private IExpOrderPriceService expOrderPriceService;
    @Autowired
    private IExpOrderMemberHistoryService expOrderMemberHistoryService;
    @Autowired
    private IUploadFileService uploadFileService;
    @Autowired
    private IStatusHistoryService statusHistoryService;
    @Autowired
    private ExpOrderMapper expOrderMapper;
    @Autowired
    private ISystemCacheService systemCacheService;
    @Autowired
    private IExpOrderLogisticsService expOrderLogisticsService;
    @Autowired
    private IExpOrderCheckWrapService expOrderCheckWrapService;
    @Autowired
    private IPersonService personService;
    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;
    @Autowired
    private ICommonService commonService;
    @Autowired
    private IExpOrderCostService expOrderCostService;
    @Autowired
    private ICostChangeRecordExpService costChangeRecordExpService;
    @Value("${redis.lock.time:5}")
    private Integer lockTime;
    @Autowired
    private RedisLockRegistry redisLockRegistry;
    @Autowired
    private IDeclarationService declarationService;
    @Autowired
    private IOverseasReceivingOrderService overseasReceivingOrderService;
    @Autowired
    private IOverseasReceivingAccountService overseasReceivingAccountService;
    @Autowired
    private IPurchaseContractService purchaseContractService;
    @Autowired
    private ITransactionFlowService transactionFlowService;
    @Autowired
    private ScmProcessor processor;
    @Autowired
    private IWriteOffService writeOffService;
    @Value("${orderMember.upload.excel.maxmum:50}")
    private int uploadMaxmum;
    @Autowired
    private ISupplierService supplierService;
    @Autowired
    private ISubjectOverseasService subjectOverseasService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long add(ExpOrderPlusDTO orderDto) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Boolean isSubmit = Objects.equals(Boolean.TRUE,orderDto.getSubmitAudit());
        //验证订单数据
        ValidatorUtils.validateEntityWithRow(orderDto,isSubmit ? UpdateGroup.class : AddGroup.class);
        ExpOrder expOrder = new ExpOrder();
        ExpOrderDTO dto = orderDto.getOrder();
        Customer customer;
        if(Objects.isNull(dto.getCustomerId())){
            customer = customerService.findByNameWithRight(dto.getCustomerName());
        }else{
            customer = customerService.getById(dto.getCustomerId());
        }
        if(Objects.isNull(customer)){
            throw new ServiceException("客户不存在");
        }
        expOrder.setCustomerId(customer.getId());
        orderDto.getOrder().setCustomerName(customer.getName());
        //订单主单赋值
        fullOrderMain(expOrder,dto);
        //物流数据
        ExpOrderLogistics expOrderLogistics = new ExpOrderLogistics();
        ExpOrderLogisticsDTO logistiscs = orderDto.getLogistiscs();
        //校验赋值国内交货
        expOrderCheckWrapService.checkWrapDomesticDeliveryLogistics(expOrderLogistics,logistiscs,isSubmit);
        //校验赋值境外交货
        expOrderCheckWrapService.checkWrapOverseasDeliveryLogistics(expOrderLogistics,logistiscs,isSubmit);

        //提交审核
        ExpOrderStatusEnum statusEnum = isSubmit ? ExpOrderStatusEnum.WAIT_PRICE : ExpOrderStatusEnum.TEMP_SAVED;//状态
        expOrder.setStatusId(statusEnum.getCode());
        expOrder.setIsExp(Boolean.FALSE);

        ExpOrderMemberSaveDTO expOrderMemberSaveDTO = expOrderMemberService.checkWrapOrderMember(expOrder,orderDto.getMembers(),orderDto.getSubmitAudit());
        try {
            super.saveNonNull(expOrder);
            //保存物流信息
            expOrderLogistics.setId(expOrder.getId());
            expOrderLogistics.setExpOrderId(expOrder.getId());
            expOrderLogisticsService.save(expOrderLogistics);
            // 新增条数
            Integer newSize = expOrderMemberService.saveMembers(expOrder,expOrderMemberSaveDTO,orderDto.getSubmitAudit());
            //关联文件信息
            uploadFileService.relateFile(expOrder.getId().toString(),orderDto.getFile());
            //记录历史状态
            statusHistoryService.saveHistory(DomainOprationEnum.EXP_ORDER_ADD,
                    expOrder.getId().toString(), ExpOrder.class.getName(),
                    statusEnum.getCode(),statusEnum.getName(),
                    Objects.equals(ExpOrderStatusEnum.TEMP_SAVED,statusEnum)?"临时保存":"提交审核"
            );
            // 计算审价
            if(Objects.equals(ExpOrderStatusEnum.WAIT_PRICE,statusEnum)){
                expOrderPriceService.calculatingPrice(expOrder);
            }
            if(newSize>0){
                //发送任务通知 通知关务归类
                classifyNotice(customer);
            }
        } catch (DuplicateKeyException e) {
            if(e.getMessage().contains("uni_exp_order_doc_no")) {
                throw new ServiceException("订单编号已经存在请刷新页面重新填写订单信息");
            }else {
                throw new ServiceException("保存失败，请检查数据是否填写正确");
            }
        }
        log.info("客户【{}】新增订单数据【{}】成功，耗时【{}】秒",customer.getName(),isSubmit ? "提交审核" : "临时保存",stopwatch.elapsed(TimeUnit.SECONDS));
        return expOrder.getId();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(ExpOrderPlusDTO orderDto) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Boolean isSubmit = Objects.equals(Boolean.TRUE,orderDto.getSubmitAudit());
        //验证订单数据
        ValidatorUtils.validateEntityWithRow(orderDto,isSubmit ? UpdateGroup.class : AddGroup.class);
        ExpOrderDTO dto = orderDto.getOrder();
        ExpOrder expOrder = super.getById(dto.getId());
        Optional.ofNullable(expOrder).orElseThrow(()->new ServiceException("订单不存在，请确认是否已经被删除"));
        //原始单据状态
        ExpOrderStatusEnum historyStatus = ExpOrderStatusEnum.of(expOrder.getStatusId());
        //验证状态是否可以执行操作
        DomainOprationEnum oprationEnum = DomainOprationEnum.EXP_ORDER_EDIT;
        DomainStatus.getInstance().check(oprationEnum, expOrder.getStatusId());
        Customer customer;
        if(Objects.isNull(dto.getCustomerId())){
            customer = customerService.findByNameWithRight(dto.getCustomerName());
        }else{
            customer = customerService.getById(dto.getCustomerId());
        }
        if(Objects.isNull(customer)){
            throw new ServiceException("客户不存在");
        }
        expOrder.setCustomerId(customer.getId());
        orderDto.getOrder().setCustomerName(customer.getName());
        //订单主单赋值
        fullOrderMain(expOrder,dto);
        //物流数据
        ExpOrderLogistics expOrderLogistics = expOrderLogisticsService.getById(expOrder.getId());
        ExpOrderLogisticsDTO logistiscs = orderDto.getLogistiscs();
        //校验赋值国内交货
        expOrderCheckWrapService.checkWrapDomesticDeliveryLogistics(expOrderLogistics,logistiscs,isSubmit);
        //校验赋值境外交货
        expOrderCheckWrapService.checkWrapOverseasDeliveryLogistics(expOrderLogistics,logistiscs,isSubmit);
        //提交审核
        ExpOrderStatusEnum statusEnum = isSubmit ? ExpOrderStatusEnum.WAIT_PRICE : ExpOrderStatusEnum.TEMP_SAVED;//状态
        expOrder.setStatusId(statusEnum.getCode());
        expOrder.setIsExp(Boolean.FALSE);

        ExpOrderMemberSaveDTO expOrderMemberSaveDTO = expOrderMemberService.checkWrapOrderMember(expOrder,orderDto.getMembers(),orderDto.getSubmitAudit());
        try {
            //修改订单主单
            super.updateById(expOrder);

            //保存物流信息
            expOrderLogistics.setExpOrderId(expOrder.getId());
            expOrderLogisticsService.updateById(expOrderLogistics);
            //修改订单明细
            Integer newSize = expOrderMemberService.saveMembers(expOrder,expOrderMemberSaveDTO,orderDto.getSubmitAudit());
            //记录历史状态
            statusHistoryService.saveHistory(oprationEnum,
                    expOrder.getId().toString(), ExpOrder.class.getName(),
                    historyStatus.getCode(),historyStatus.getName(),
                    statusEnum.getCode(),statusEnum.getName(),
                    Objects.equals(ExpOrderStatusEnum.TEMP_SAVED,statusEnum)?"临时保存":"提交审核"
            );
            //验证是否需要审价
            if(Objects.equals(ExpOrderStatusEnum.WAIT_PRICE,statusEnum)){
                expOrderPriceService.calculatingPrice(expOrder);
            }
            //清除修改任务提示
            taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.EXPORDER.getCode(), expOrder.getId(), DomainOprationEnum.EXP_ORDER_EDIT.getCode());
            if(newSize>0){
                classifyNotice(customer);
            }
        } catch (DuplicateKeyException e) {
            if(e.getMessage().contains("uni_exp_order_doc_no")) {
                throw new ServiceException("订单编号已经存在请刷新页面重新填写订单信息");
            }else {
                throw new ServiceException("保存失败，请检查数据是否填写正确");
            }
        }
        log.info("客户【{}】修改订单数据【{}】成功，耗时【{}】秒",customer.getName(),isSubmit ? "提交审核" : "临时保存",stopwatch.elapsed(TimeUnit.SECONDS));
    }

    //赋值订单主单
    private void fullOrderMain(ExpOrder expOrder,ExpOrderDTO dto){

        // 客户协议报价
        ExpAgreement agreement = expAgreementService.getById(dto.getAgreementId());
        TsfPreconditions.checkArgument(Objects.nonNull(agreement),new ServiceException("客户协议报价不存在"));
        TsfPreconditions.checkArgument(Objects.equals(expOrder.getCustomerId(),agreement.getCustomerId()),new ServiceException("客户协议报价不存在"));
        expOrder.setAgreementId(agreement.getId());

        //订单号
        expOrder.setDocNo(dto.getDocNo());
        //客户单号
        expOrder.setClientNo(StringUtils.isNotEmpty(dto.getClientNo()) ? dto.getClientNo() : dto.getDocNo());
        //客户单号不允许存在重复的
        Example clientExample = Example.builder(ExpOrder.class).where(TsfWeekendSqls.<ExpOrder>custom()
                .andEqualTo(false,ExpOrder::getClientNo,expOrder.getClientNo())).build();
        List<ExpOrder> clientExpOrders = expOrderMapper.selectByExampleAndRowBounds(clientExample,new RowBounds(0,2));
        if(Objects.isNull(dto.getId())) {
            //新增
            TsfPreconditions.checkArgument(CollUtil.isEmpty(clientExpOrders), new ServiceException(StrUtil.format("客户单号【{}】已经存在", expOrder.getClientNo())));
        } else {
            ValidatorUtils.isTrue(clientExpOrders.stream().filter(r->!Objects.equals(r.getId().longValue(),expOrder.getId())).count() <= 0,()->new ServiceException(StrUtil.format("客户单号【{}】已经存在", expOrder.getClientNo())));
        }
        expOrder.setSubjectOverseasType(dto.getSubjectOverseasType());
        if(Objects.equals(dto.getSubjectOverseasType(),"1")) { //主体的境外公司
            //境外客户
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getSupplierName()),new ServiceException("请选择境外客户"));
            Supplier supplier = supplierService.findByCustomerNameAndSupplierName(dto.getCustomerName(),dto.getSupplierName());
            TsfPreconditions.checkArgument(Objects.nonNull(supplier),new ServiceException("境外客户不存在，请至 客户关系》供应商 维护"));
            expOrder.setSupplierId(supplier.getId());
        } else { //客户的境外公司
            expOrder.setSupplierId(null);
        }
        //订单日期
        if(Objects.nonNull(dto.getOrderDate())) {
            expOrder.setOrderDate(LocalDateTimeUtils.convertDateToLDT(dto.getOrderDate()));
        } else {
            expOrder.setOrderDate(LocalDateTimeUtils.convertLocalDate());
        }
        //报关类型
        expOrder.setDeclareType(dto.getDeclareType());
        //成交方式
        expOrder.setTransactionMode(dto.getTransactionMode());
        //运输方式
        BaseDataVO cusTrafModeData = systemCacheService.getBaseDataByTpeAndCode(BaseDataTypeEnum.CusTrafMode.getCode(),dto.getCusTrafMode());
        Optional.ofNullable(cusTrafModeData).orElseThrow(()->new ServiceException("运输方式错误"));
        expOrder.setCusTrafMode(cusTrafModeData.getCode());
        expOrder.setCusTrafModeName(cusTrafModeData.getName());
        //境内货源地
        expOrder.setDistrictCode(dto.getDistrictCode());
        expOrder.setDistrictCodeName(dto.getDistrictCodeName());
        //运抵国(地区)
        expOrder.setCusTradeCountry(dto.getCusTradeCountry());
        expOrder.setCusTradeCountryName(dto.getCusTradeCountryName());
        //贸易国(地区)
        expOrder.setCusTradeNationCode(dto.getCusTradeNationCode());
        expOrder.setCusTradeNationCodeName(dto.getCusTradeNationCodeName());
        //指运港
        expOrder.setDistinatePort(dto.getDistinatePort());
        expOrder.setDistinatePortName(dto.getDistinatePortName());
        //最终目的国(地区)
        expOrder.setDestinationCountry(dto.getDestinationCountry());
        expOrder.setDestinationCountryName(dto.getDestinationCountryName());
        //校验赋值币制信息
        String currencyName = dto.getCurrencyName();
        TsfPreconditions.checkArgument(StringUtils.isNotEmpty(currencyName),new ServiceException("币制不能为空"));
        Currency currency = currencyService.findByName(currencyName);
        TsfPreconditions.checkArgument(Objects.nonNull(currency),new ServiceException(String.format("【%s】币制不存在",currencyName)));
        expOrder.setCurrencyId(currency.getId());
        expOrder.setCurrencyName(currency.getName());
        //(外贸合同卖方)
        expOrder.setSubjectId(dto.getSubjectId());
        //(外贸合同买方)
        if(Objects.equals(dto.getSubjectOverseasType(),"1")) { //主体的境外公司
            Optional.ofNullable(dto.getSubjectOverseasId()).orElseThrow(()->new ServiceException("请选择外贸合同买方"));
            expOrder.setSubjectOverseasId(dto.getSubjectOverseasId());
            SubjectOverseas subjectOverseas = subjectOverseasService.getById(dto.getSubjectOverseasId());
            Optional.ofNullable(subjectOverseas).orElseThrow(()->new ServiceException("外贸合同买方不存在，请重新选择"));
            expOrder.setSubjectOverseasName(subjectOverseas.getName());
        } else { //客户的境外公司
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getSubjectOverseasName()),new ServiceException("请选择外贸合同买方"));
            Supplier supplier = supplierService.findByCustomerNameAndSupplierName(dto.getCustomerName(),dto.getSubjectOverseasName());
            TsfPreconditions.checkArgument(Objects.nonNull(supplier),new ServiceException("境外客户不存在，请至 客户关系》供应商 维护"));
            expOrder.setSubjectOverseasId(supplier.getId());
            expOrder.setSubjectOverseasName(supplier.getName());
        }
        expOrder.setMemo(dto.getMemo());//备注
        expOrder.setPurchaseContractNo("");//采购合同号
    }

    @Override
    public PageInfo<ExpOrderListVO> list(ExpOrderQTO qto) {
        //如果传了动态日期字段，检查是否包含该字段以防数据库报错
        if(StringUtils.isNotEmpty(qto.getQueryDate())) {
            TsfPreconditions.checkArgument(ClassUtil.containsField(ExpOrder.class,qto.getQueryDate()),new ServiceException("日期查询类型参数错误"));
        }
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        Map<String,Object> params = beanMapper.map(qto,Map.class);
        List<ExpOrderListVO> list = expOrderMapper.list(params);
        //取客户对应的销售和商务人员名称
        if(CollUtil.isNotEmpty(list)) {
            Set<Long> personSet = Sets.newHashSet(list.stream().map(ExpOrderListVO::getSalePersonId).collect(Collectors.toSet()));
            personSet.addAll(list.stream().map(ExpOrderListVO::getBusPersonId).collect(Collectors.toSet()));
            Map<Long, SimplePersonInfo> personMap = personService.findByIds(personSet);
            if(CollectionUtil.isNotEmpty(personMap)) {
                list.stream().forEach(r->{
                    SimplePersonInfo simpleSalePersonInfo = personMap.get(r.getSalePersonId());
                    SimplePersonInfo simpleBusPersonInfo = personMap.get(r.getBusPersonId());
                    r.setSalePersonName(Objects.nonNull(simpleSalePersonInfo) ? simpleSalePersonInfo.getName() : null);
                    r.setBusPersonName(Objects.nonNull(simpleBusPersonInfo) ? simpleBusPersonInfo.getName() : null);
                });
            }
        }
        return new PageInfo<>(list);
    }

    @Override
    public ExpOrderVO detail(Long id, String operation) {
        ExpOrderVO order = expOrderMapper.detail(id);
        TsfPreconditions.checkArgument(Objects.nonNull(order),new ServiceException("订单不存在"));
        //验证状态是否可以执行操作
        DomainOprationEnum domainOpration = DomainOprationEnum.of(operation);
        DomainStatus.getInstance().check(domainOpration, order.getStatusId());
        return order;
    }

    @Override
    public ExpOrderVO detail(Long id) {
        return detail(id,null);
    }

    @Override
    public ExpOrderPlusVO detailPlus(Long id, String operation) {
        ExpOrderVO expOrderVO = detail(id,operation);
        ExpOrderLogisticsVO logistiscs = expOrderLogisticsService.findById(id);
        List<ExpOrderMemberVO> expOrderMemberVOS = expOrderMemberService.findOrderMaterielMember(id);
        return new ExpOrderPlusVO(expOrderVO,logistiscs,expOrderMemberVOS);
    }

    @Override
    public ExpOrderPlusVO detailPlus(Long id) {
        ExpOrderVO expOrderVO = detail(id);
        ExpOrderLogisticsVO logistiscs = expOrderLogisticsService.findById(id);
        List<ExpOrderMemberVO> expOrderMemberVOS = expOrderMemberService.findOrderMaterielMember(id);
        return new ExpOrderPlusVO(expOrderVO,logistiscs,expOrderMemberVOS);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeOrder(Long id) {
        ExpOrder order = super.getById(id);
        removeOrder(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeOrder(ExpOrder expOrder) {
        TsfPreconditions.checkArgument(Objects.nonNull(expOrder),new ServiceException("订单不存在"));
        //验证状态是否可以执行操作
        DomainStatus.getInstance().check(DomainOprationEnum.EXP_ORDER_REMOVE, expOrder.getStatusId());
        //删除订单明细信息
        expOrderMemberService.removeByExpOrderId(expOrder.getId());
        //删除物流信息
        expOrderLogisticsService.removeById(expOrder.getId());
        // 删除订单明细历史数据
        expOrderMemberHistoryService.removeByOrderId(expOrder.getId());
        // 删除审价
        expOrderPriceService.removeByOrderId(expOrder.getId());
        // 删除费用信息
        expOrderCostService.removeByOrderId(expOrder.getId());
        // 删除费用变更记录
        costChangeRecordExpService.removeByOrderId(expOrder.getId());

        //删除历史状态
        statusHistoryService.removeHistory(expOrder.getId().toString(), ExpOrder.class.getName());
        try {
            super.removeById(expOrder.getId());
        } catch (DataIntegrityViolationException e) {
            log.error("删除订单出错：",e);
            throw new ServiceException("当前订单已经在操作流程中，请勿删除");
        }
        //清空任务通知
        clearTaskNotice(expOrder.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void orderPricing(TaskDTO dto){
        commonService.changeDocumentStatus(dto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void examine(TaskDTO dto) {
        commonService.changeDocumentStatus(dto);
        //修改后单据状态
        ExpOrderStatusEnum nowStatusEnum = ExpOrderStatusEnum.of(dto.getNewStatusCode());
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        switch (nowStatusEnum){
            case WAIT_INSPECTION://审核通过待验货
                ExpOrder expOrderINS = super.getById(dto.getDocumentId());
                //计算订单费用
                expOrderCostService.calculationCost(expOrderINS);
                //任务通知
                taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
                taskNoticeContentDTO.setDocumentId(expOrderINS.getId().toString());
                taskNoticeContentDTO.setCustomerId(expOrderINS.getCustomerId());
                taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_INSPECTION.getCode());
                taskNoticeContentDTO.setContent(String.format("出口订单【%s】已审核通过，请尽快确认验货", expOrderINS.getDocNo()));
                taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrderINS.getDocNo()));
                taskNoticeContentService.add(taskNoticeContentDTO);
                break;
            case WAIT_EDIT://退回修改
                //任务通知
                ExpOrder expOrderEdit = super.getById(dto.getDocumentId());
                taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
                taskNoticeContentDTO.setDocumentId(expOrderEdit.getId().toString());
                taskNoticeContentDTO.setCustomerId(expOrderEdit.getCustomerId());
                taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_EDIT.getCode());
                taskNoticeContentDTO.setContent(String.format("出口订单【%s】被退回：%s", expOrderEdit.getDocNo(),dto.getMemo()));
                taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrderEdit.getDocNo()));
                taskNoticeContentService.add(taskNoticeContentDTO);
                break;
            case WAIT_PRICE://退回审价
                ExpOrder expOrderPrice = super.getById(dto.getDocumentId());
                //进入审价流程
                expOrderPriceService.calculatingPrice(expOrderPrice,Boolean.TRUE);
                break;
            default:
                throw new ServiceException("当前状态不允许此操作");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void shareNetWeight(Long id, BigDecimal netWeight) {
        if(netWeight.compareTo(BigDecimal.ZERO)!=1){
            throw new ServiceException("总净重必须大于零");
        }
        ExpOrder order = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(order),new ServiceException("订单不存在"));
        order.setTotalNetWeight(BigDecimal.ZERO);
        List<ExpOrderMember> members = expOrderMemberService.getByOrderId(id);
        if(CollUtil.isNotEmpty(members)){
            BigDecimal quantity = BigDecimal.ZERO;
            for(ExpOrderMember iom : members){
                quantity = quantity.add(iom.getQuantity());
            }
            List<ExpOrderMember> updates = Lists.newArrayList();
            for(ExpOrderMember iom : members){
                BigDecimal shareVal = netWeight.divide(quantity,8,BigDecimal.ROUND_HALF_UP).multiply(iom.getQuantity()).setScale(4,BigDecimal.ROUND_HALF_UP);
                if(shareVal.compareTo(BigDecimal.valueOf(0.01))<0){
                    shareVal = BigDecimal.valueOf(0.01);
                }
                ExpOrderMember update = new ExpOrderMember();
                update.setId(iom.getId());
                update.setNetWeight(shareVal);
                updates.add(update);
                order.setTotalNetWeight(order.getTotalNetWeight().add(update.getNetWeight()).setScale(4,BigDecimal.ROUND_HALF_UP));
            }
            expOrderMemberService.batchUpdatePartData(updates);
        }
        super.updateById(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void shareGrossWeight(Long id, BigDecimal grossWeight) {
        if(grossWeight.compareTo(BigDecimal.ZERO)!=1){
            throw new ServiceException("总毛重必须大于零");
        }
        ExpOrder order = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(order),new ServiceException("订单不存在"));
        order.setTotalCrossWeight(BigDecimal.ZERO);
        List<ExpOrderMember> members = expOrderMemberService.getByOrderId(id);
        if(CollUtil.isNotEmpty(members)){
            BigDecimal quantity = BigDecimal.ZERO;
            for(ExpOrderMember iom : members){
                quantity = quantity.add(iom.getQuantity());
            }
            List<ExpOrderMember> updates = Lists.newArrayList();
            for(ExpOrderMember iom : members){
                BigDecimal shareVal = grossWeight.divide(quantity,8,BigDecimal.ROUND_HALF_UP).multiply(iom.getQuantity()).setScale(4,BigDecimal.ROUND_HALF_UP);
                if(shareVal.compareTo(BigDecimal.valueOf(0.01))<0){
                    shareVal = BigDecimal.valueOf(0.01);
                }
                ExpOrderMember update = new ExpOrderMember();
                update.setId(iom.getId());
                update.setGrossWeight(shareVal);
                updates.add(update);
                order.setTotalCrossWeight(order.getTotalCrossWeight().add(update.getGrossWeight()).setScale(4,BigDecimal.ROUND_HALF_UP));
            }
            expOrderMemberService.batchUpdatePartData(updates);
        }
        super.updateById(order);
    }

    //清空任务通知
    public void clearTaskNotice(Long id){
        taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.EXPORDER.getCode(), id, "");
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveInspectionMemberEdit(ExpOrderInspectionMemberDTO dto) {
        ExpOrder expOrder = super.getById(dto.getOrderId());
        TsfPreconditions.checkArgument(Objects.nonNull(expOrder),new ServiceException("订单不存在"));
        //验证状态是否可以执行操作
        DomainStatus.getInstance().check(DomainOprationEnum.EXP_ORDER_INSPECTION, expOrder.getStatusId());
        //订单明细数据
        List<ExpOrderMemberDTO> paramMembers = dto.getMembers();
        //校验赋值订单明细
        ExpOrderMemberSaveDTO expOrderMemberSaveDTO = expOrderMemberService.checkWrapOrderMember(expOrder,paramMembers,Boolean.TRUE);
        if(CollUtil.isNotEmpty(expOrderMemberSaveDTO.getDeleteOrderMembers())){
            throw new ServiceException("订单明细数据存在变更请刷新页面重试");
        }
        List<ExpOrderMember> expOrderMemberList = expOrderMemberSaveDTO.getSaveOrderMembers();
        //修改订单明细
        Integer newSize = expOrderMemberService.saveInspectionMembers(expOrder,expOrderMemberList);
        //修改订单主单
        super.updateById(expOrder);
        Customer customer = customerService.getById(expOrder.getCustomerId());
        if(newSize>0){
            //发送任务通知 通知关务归类
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.MATERIEL_EXP.getCode());
            taskNoticeContentDTO.setDocumentId("-1");
            taskNoticeContentDTO.setCustomerId(customer.getId());
            taskNoticeContentDTO.setOperationCode(DomainOprationEnum.MATERIEL_CLASSIFY_EXP.getCode());
            taskNoticeContentDTO.setContent(String.format("客户【%s】有新物料需要您归类，请立即处理。",customer.getName()));
            Map<String,Object> queryParamsMap = new LinkedHashMap<String,Object>(){{
                put("customerName",customer.getName());
                put("statusId", MaterielStatusEnum.WAIT_CLASSIFY.getCode());
            }};
            taskNoticeContentDTO.setQueryParamsMap(queryParamsMap);
            taskNoticeContentDTO.setIsExeRemove(Boolean.TRUE);
            taskNoticeContentService.add(taskNoticeContentDTO);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmInspection(Long id) {
        ExpOrder expOrder = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(expOrder),new ServiceException("订单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.EXP_ORDER_INSPECTION;
        //验证状态是否可以执行操作
        DomainStatus.getInstance().check(oprationEnum, expOrder.getStatusId());
        TsfPreconditions.checkArgument(expOrder.getTotalCartonNum()>0,new ServiceException("总箱数不能为零"));
        TsfPreconditions.checkArgument(expOrder.getTotalNetWeight().compareTo(BigDecimal.ZERO)==1,new ServiceException("总净重必须大于零"));
        TsfPreconditions.checkArgument(expOrder.getTotalCrossWeight().compareTo(BigDecimal.ZERO)==1,new ServiceException("总毛重必须大于零"));
        TsfPreconditions.checkArgument(expOrder.getTotalCrossWeight().compareTo(expOrder.getTotalNetWeight())>=0,new ServiceException("总毛重不能小于总净重"));
        //原始单据状态
        ExpOrderStatusEnum historyStatus = ExpOrderStatusEnum.of(expOrder.getStatusId());
        //更新订单状态(待确认出口)
        ExpOrderStatusEnum statusEnum = ExpOrderStatusEnum.WAIT_CONFIRM_EXP;
        expOrder.setStatusId(statusEnum.getCode());
        //计算订单费用
        expOrderCostService.calculationCost(expOrder);
        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                expOrder.getId().toString(),ExpOrder.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),
                "验货确认"
        );
        //清除任务提示
        clearTaskNotice(expOrder.getId());
        //任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
        taskNoticeContentDTO.setDocumentId(expOrder.getId().toString());
        taskNoticeContentDTO.setCustomerId(expOrder.getCustomerId());
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_CONFIRMIMP.getCode());
        taskNoticeContentDTO.setContent(String.format("出口订单【%s】已验货完成，请尽快确认出口", expOrder.getDocNo()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrder.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmExp(TaskDTO dto) {
        String lockKey = DistributedLockEnum.EXP_ORDER_CONFIRM.getCode() + ":" + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("未获取到锁，订单：【%s】", dto.getDocumentId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            ExpOrder expOrder = commonService.changeDocumentStatus(dto);
            //修改后单据状态
            ExpOrderStatusEnum nowStatusEnum = ExpOrderStatusEnum.of(dto.getNewStatusCode());
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            switch (nowStatusEnum){
                case WAIT_EDIT://退回修改
                    //任务通知
                    taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
                    taskNoticeContentDTO.setDocumentId(expOrder.getId().toString());
                    taskNoticeContentDTO.setCustomerId(expOrder.getCustomerId());
                    taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_EDIT.getCode());
                    taskNoticeContentDTO.setContent(String.format("出口订单【%s】被退回：%s", expOrder.getDocNo(),dto.getMemo()));
                    taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrder.getDocNo()));
                    taskNoticeContentService.add(taskNoticeContentDTO);
                    break;
                case WAIT_INSPECTION://待验货
                    //任务通知
                    taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
                    taskNoticeContentDTO.setDocumentId(expOrder.getId().toString());
                    taskNoticeContentDTO.setCustomerId(expOrder.getCustomerId());
                    taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_INSPECTION.getCode());
                    taskNoticeContentDTO.setContent(String.format("出口订单【%s】需要您重新验货确认：%s", expOrder.getDocNo(),dto.getMemo()));
                    taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrder.getDocNo()));
                    taskNoticeContentService.add(taskNoticeContentDTO);
                    break;
                case WAIT_DECLARE://待报关
                    //验证订单日期
                    if(!LocalDateTimeUtils.formatShort(expOrder.getOrderDate()).equals(LocalDateTimeUtils.formatNow("yyyy-MM-dd"))){
                        throw new ServiceException("订单日期不是最新日期，为防止汇率变化请退回后重新提交");
                    }
                    //获取未锁定的费用信息写入流水
                    List<ExpOrderCostVO> orderCosts = expOrderCostService.obtainOrderNotLockCosts(expOrder.getId());
                    // 锁定系统费用
                    expOrderCostService.expLockCost(expOrder.getId());
                    if(CollectionUtil.isNotEmpty(orderCosts)){
                        BigDecimal amount = BigDecimal.ZERO;
                        List<String> costMemos = Lists.newArrayList();
                        for(ExpOrderCostVO oc : orderCosts){
                            costMemos.add(oc.getExpenseSubjectName()+":"+oc.getReceAmount());
                            amount = amount.add(oc.getReceAmount()).setScale(2,BigDecimal.ROUND_HALF_UP);
                        }
                        TransactionFlowDTO param = new TransactionFlowDTO();
                        param.setCustomerId(expOrder.getCustomerId());
                        param.setTransactionType(TransactionTypeEnum.EXP_ORDER.getCode());
                        param.setTransactionCategory(TransactionCategoryEnum.OUTCOME.getCode());
                        param.setTransactionNo(expOrder.getDocNo());
                        param.setAmount(amount);
                        param.setMemo(String.join(";",costMemos));
                        transactionFlowService.recordTransactionFlow(param);
                    }
                    // 根据订单生成报关单
                    Declaration declaration = declarationService.createByExpOrder(expOrder);
                    //修改订单
                    expOrder.setIsExp(Boolean.TRUE);//确定出口
                    expOrder.setDeclarationNo(declaration.getDocNo());//报关单号
                    super.updateById(expOrder);

                    //通知核销
                    CostWriteOffDTO cwdto = new CostWriteOffDTO();
                    cwdto.setTenant("");
                    cwdto.setCustomerId(expOrder.getCustomerId());
                    processor.sendCostWriteOff(cwdto);
                    break;
                default:
                    throw new ServiceException("当前状态不允许此操作");
            }
        }catch (InterruptedException e) {
            log.error("出口订单确认出口获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally{
            //释放锁
            lock.unlock();
        }
    }

    @Override
    public List<BindOrderVO> waybillBindedOrderList(String transportNo) {
        return expOrderMapper.listBinded(transportNo);
    }

    @Override
    public List<BindOrderVO> waybillCanBindOrderList(Boolean isCharterCar) {
        return expOrderMapper.listCanBind(isCharterCar);
    }

    @Override
    public void resetTransportNo(Long id) {
        expOrderMapper.resetTransportNo(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setupDeclaration(SetupDeclarationDTO dto) {
        ExpOrder expOrder = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(expOrder),new ServiceException("订单不存在"));
        TsfPreconditions.checkArgument(Objects.equals(expOrder.getIsExp(),Boolean.FALSE),new ServiceException("当前订单已经生成报关单无法修改"));
        //订单成交方式
        TransactionModeEnum transactionMode = TransactionModeEnum.of(expOrder.getTransactionMode());
        switch (transactionMode){
            case FOB:
                expOrder.setTransCosts("");//运费
                expOrder.setInsuranceCosts("");//保费
                expOrder.setMiscCosts("");//杂费
                break;
            case CIF:
                expOrder.setTransCosts(dto.getFeeCurr()+"/"+dto.getFeeRate()+"/"+dto.getFeeMark());//运费
                expOrder.setInsuranceCosts(dto.getInsurCurr()+"/"+dto.getInsurRate()+"/"+dto.getInsurMark());//保费
                expOrder.setMiscCosts(dto.getOtherCurr()+"/"+dto.getOtherRate()+"/"+dto.getOtherMark());//杂费
                break;
            default:
                throw new ServiceException("成交方式错误");
        }
        //报关单模板
        expOrder.setDeclarationTempId(dto.getTempId());
        super.updateById(expOrder);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateTransportNo(Long orderId, String transportNo) {
        return expOrderMapper.updateTransportNo(orderId,transportNo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void backInspection(Long id, String info) {
        String lockKey = DistributedLockEnum.EXP_ORDER_BACK_INSPECTION.getCode() + ":" + id;
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if(!isLock) {
                log.error(String.format("未获取到锁，订单：【%s】",id));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            ExpOrder expOrder = super.getById(id);
            // 原始单据状态
            ExpOrderStatusEnum historyStatus = ExpOrderStatusEnum.of(expOrder.getStatusId());
            ExpOrderStatusEnum WAIT_DECLARE = ExpOrderStatusEnum.WAIT_DECLARE;
            if(Objects.isNull(expOrder) || !Objects.equals(WAIT_DECLARE,historyStatus)){
                throw new ServiceException(String.format("订单不存在或不处于%s状态",WAIT_DECLARE.getName()));
            }
            ExpOrderStatusEnum nowStatusEnum = ExpOrderStatusEnum.WAIT_INSPECTION;

            // 反冲销流水
            List<String> collectionSources = Lists.newArrayList(CollectionSourceEnum.PAYMENT.getCode(),CollectionSourceEnum.OTHER.getCode());
            List<ExpOrderCostVO> orderCosts = expOrderCostService.findCostByOrderIdAndCollectionSource(expOrder.getId(), collectionSources);
            BigDecimal amount = BigDecimal.ZERO;
            List<String> costMemos = Lists.newArrayList();
            // 需要反核销的费用ID
            List<Long> cancelWriteOffCostIds = Lists.newArrayList();
            // 需要解锁的费用ID
            List<Long> cancelLockCostIds = Lists.newArrayList();
            for(ExpOrderCostVO oc : orderCosts){
                costMemos.add(oc.getExpenseSubjectName()+":"+oc.getReceAmount());
                amount = amount.add(oc.getReceAmount()).setScale(2,BigDecimal.ROUND_HALF_UP);
                cancelLockCostIds.add(oc.getId());
                // 核销金额大于零
                if(oc.getAcceAmount().compareTo(BigDecimal.ZERO) == 1){
                    cancelWriteOffCostIds.add(oc.getId());
                }
            }
            if(CollUtil.isNotEmpty(costMemos)){
                TransactionFlowDTO param = new TransactionFlowDTO();
                param.setCustomerId(expOrder.getCustomerId());
                param.setTransactionType(TransactionTypeEnum.EXP_ORDER.getCode());
                param.setTransactionCategory(TransactionCategoryEnum.INCOME.getCode());
                param.setTransactionNo(expOrder.getDocNo());
                param.setAmount(amount);
                param.setMemo("出口退单:"+String.join(";",costMemos));
                transactionFlowService.recordTransactionFlow(param);
            }
            // 解锁费用
            if(CollUtil.isNotEmpty(cancelLockCostIds)){
                expOrderCostService.expUnLockCost(cancelLockCostIds);
            }
            // 反核销
            if(CollUtil.isNotEmpty(cancelWriteOffCostIds)){
                cancelWriteOffCostIds.stream().forEach(costId ->{
                    writeOffService.cancelWriteOffByExpOrderCost(expOrderCostService.getById(costId));
                });
            }

            //修改订单
            expOrder.setStatusId(nowStatusEnum.getCode());// 待验货
            expOrder.setIsExp(Boolean.FALSE);//确定进口
            expOrder.setDeclarationNo("");//报关单号
            super.updateById(expOrder);

            //记录历史状态
            statusHistoryService.saveHistory(DomainOprationEnum.EXP_ORDER_CONFIRMIMP_BACK,
                    expOrder.getId().toString(),ExpOrder.class.getName(),
                    historyStatus.getCode(),historyStatus.getName(),
                    nowStatusEnum.getCode(),nowStatusEnum.getName(),info
            );

            //任务通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.EXPORDER.getCode());
            taskNoticeContentDTO.setDocumentId(expOrder.getId().toString());
            taskNoticeContentDTO.setCustomerId(expOrder.getCustomerId());
            taskNoticeContentDTO.setOperationCode(DomainOprationEnum.EXP_ORDER_INSPECTION.getCode());
            taskNoticeContentDTO.setContent(String.format("出口订单【%s】需要您重新验货确认：%s", expOrder.getDocNo(),info));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",expOrder.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);

        }catch (InterruptedException e) {
            log.error("退单获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        }finally{
            //释放锁
            lock.unlock();
        }
    }

    @Override
    public int getWaybillBindOrderNumber(String transportNo) {
        TsfWeekendSqls wheres = TsfWeekendSqls.<ExpOrder>custom().andEqualTo(false,ExpOrder::getTransportNo,transportNo);
        return expOrderMapper.selectCountByExample(Example.builder(ExpOrder.class).where(wheres).build());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void declarationCompleted(Long orderId) {
        TaskDTO taskDTO = new TaskDTO();
        taskDTO.setDocumentId(orderId);
        taskDTO.setDocumentClass(DomainTypeEnum.EXPORDER.getCode());
        taskDTO.setOperation(DomainOprationEnum.EXP_ORDER_COMPLETE_DEC.getCode());
        taskDTO.setNewStatusCode(ExpOrderStatusEnum.WAIT_CLEARANCE.getCode());
        taskDTO.setMemo("报关单申报完成");
        //可能流程自动触发
        if(StringUtils.isEmpty(SecurityUtil.getCurrentPersonName())){
            taskDTO.setOperator("系统");
        }
        ExpOrder order = commonService.changeDocumentStatus(taskDTO);
        order.setClearanceDate(new Date());
        super.updateById(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void expOrderClearance(List<BindOrderVO> bindedOrders) {
        ExpOrderStatusEnum statusEnum = ExpOrderStatusEnum.CLEARED;
        bindedOrders.stream().forEach(border ->{
            ExpOrderStatusEnum historyStatus = ExpOrderStatusEnum.of(border.getStatusId());
            TsfPreconditions.checkArgument(Objects.equals(ExpOrderStatusEnum.WAIT_CLEARANCE,historyStatus),new ServiceException(String.format("订单【%s】不处于待通关状态",border.getDocNo())));
            ExpOrder updateOrder = new ExpOrder();
            updateOrder.setId(border.getId());
            updateOrder.setStatusId(statusEnum.getCode());
            expOrderMapper.updateByPrimaryKeySelective(updateOrder);

            //记录历史状态
            statusHistoryService.saveHistory(DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_SIGN,
                    border.getId().toString(),ExpOrder.class.getName(),
                    historyStatus.getCode(),historyStatus.getName(),
                    statusEnum.getCode(),statusEnum.getName(),
                    String.format("跨境运单签收自动触发"),"系统");
        });
    }

    @Override
    public ExpOrder findByDocNo(String docNo) {
        ExpOrder query = new ExpOrder();
        query.setDocNo(docNo);
        return expOrderMapper.selectOne(query);
    }

    @Override
    public List<ExpOrderAdjustCostVO> selectAdjustOrder(String orderNo) {
        Map<String,Object> params = Maps.newHashMap();
        params.put("orderNo",orderNo);
        return expOrderMapper.selectAdjustOrder(params);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateByPurchaseContract(ExpOrder order) {
        expOrderMapper.updateByPurchaseContract(order);
    }

    @Override
    public List<OverseasReceivingOrderVO> collectionClaimedAmount(ClaimedAmountDTO dto) {
        return expOrderMapper.collectionClaimedAmount(dto);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public BigDecimal overseasAccountClaimed(ClaimedAmountPlusDTO dto) {
        BigDecimal totalClaimed = BigDecimal.ZERO;
        for(ClaimedAmountOrderDTO orderDTO : dto.getOrders()){
            ExpOrder order = super.getById(orderDTO.getOrderId());
            BigDecimal surplus = order.getDecTotalPrice().subtract(order.getAmountCollected()).setScale(2,BigDecimal.ROUND_HALF_UP);
            if(orderDTO.getUnclaimedValue().compareTo(BigDecimal.ZERO)!=1){
                throw new ServiceException(String.format("订单【%s】本次认领金额必须大于零",order.getDocNo()));
            }
            if(orderDTO.getUnclaimedValue().compareTo(surplus)==1){
                throw new ServiceException(String.format("订单【%s】本次认领金额不能大于：%s",order.getDocNo(),StringUtils.formatCurrency(surplus)));
            }
            // 写入明细
            orderDTO.setOrderNo(order.getDocNo());
            expOrderMemberService.overseasAccountClaimed(orderDTO);
            order.setAmountCollected(order.getAmountCollected().add(orderDTO.getUnclaimedValue()));
            super.updateById(order);
            totalClaimed = totalClaimed.add(orderDTO.getUnclaimedValue()).setScale(2,BigDecimal.ROUND_HALF_UP);

            OverseasReceivingOrder overseasReceivingOrder = overseasReceivingOrderService.findByAccountIdAndExpOrderId(dto.getId(),order.getId());
            if(Objects.isNull(overseasReceivingOrder)){
                overseasReceivingOrder = new OverseasReceivingOrder();
                overseasReceivingOrder.setOverseasReceivingId(dto.getId());
                overseasReceivingOrder.setExpOrderId(order.getId());
                overseasReceivingOrder.setOrderNo(order.getDocNo());
                overseasReceivingOrder.setAccountValue(orderDTO.getUnclaimedValue());
                overseasReceivingOrder.setSettlementValue(BigDecimal.ZERO);
                overseasReceivingOrderService.save(overseasReceivingOrder);
            }else{
                overseasReceivingOrder.setAccountValue(overseasReceivingOrder.getAccountValue().add(orderDTO.getUnclaimedValue()).setScale(2,BigDecimal.ROUND_HALF_UP));
                overseasReceivingOrderService.updateById(overseasReceivingOrder);
            }
        }
        return totalClaimed;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void clearClaimOrder(OverseasReceivingOrder receivingOrder) {
        ExpOrder order = super.getById(receivingOrder.getExpOrderId());
        if(receivingOrder.getAccountValue().compareTo(order.getAmountCollected()) == 1){
            throw new ServiceException(String.format("订单【%s】本次最多可取消的认领金额为：%s",order.getDocNo(),StringUtils.formatCurrency(order.getAmountCollected())));
        }
        // 取消明细认领金额
        expOrderMemberService.clearClaimOrder(receivingOrder);
        order.setAmountCollected(order.getAmountCollected().subtract(receivingOrder.getAccountValue()).setScale(2,BigDecimal.ROUND_HALF_UP));
        super.updateById(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void writeSettlementValue(Long id, BigDecimal settlementValueUsd, BigDecimal settlementValueCny) {
        ExpOrder order = super.getById(id);
        order.setSettleAccountUsd(order.getSettleAccountUsd().add(settlementValueUsd).setScale(2,BigDecimal.ROUND_HALF_UP));
        order.setSettleAccountCny(order.getSettleAccountCny().add(settlementValueCny).setScale(2,BigDecimal.ROUND_HALF_UP));
        order.setIsSettleAccount(Boolean.FALSE);
        // 收款完成(已经认领到订单)
        if(order.getDecTotalPrice().compareTo(order.getAmountCollected()) == 0){
            // 查询收款单是否都完成结汇
            order.setIsSettleAccount(overseasReceivingAccountService.findOrderNoIsSettlement(order.getDocNo()));
            // 结汇完成并且已经生成采购合同
            if(Objects.equals(order.getIsSettleAccount(),Boolean.TRUE)&&StringUtils.isNotEmpty(order.getPurchaseContractNo())){
                // 实际采购总价
                order.setActualPurchaseCny(purchaseContractService.calculationActualPurchaseValue(order.getId(),order.getSettleAccountCny()));
                // 计算票差
                order.setDifferenceVal(order.getActualPurchaseCny().subtract(
                        order.getPurchaseCny().subtract(order.getAdjustmentVal()).setScale(2,BigDecimal.ROUND_HALF_UP)
                ).setScale(2,BigDecimal.ROUND_HALF_UP));
                // 退税金额=收票金额-结汇金额-代理费-代垫费
                // 调整为 退税金额=收票金额-结汇金额
                PurchaseContract purchaseContract = purchaseContractService.findByDocNo(order.getPurchaseContractNo());
                /*
                order.setActualDrawbackValue(
                        purchaseContract.getInvoiceVal().subtract(order.getSettleAccountCny()).subtract(purchaseContract.getAgentFee()).subtract(purchaseContract.getMatFee()).setScale(2,BigDecimal.ROUND_HALF_UP)
                );
                 */
                order.setActualDrawbackValue(
                        purchaseContract.getInvoiceVal().subtract(order.getSettleAccountCny()).setScale(2,BigDecimal.ROUND_HALF_UP)
                );
            }
        }
        super.updateById(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void canWriteSettlementValue(Long id, BigDecimal settlementValueUsd, BigDecimal settlementValueCny) {
        ExpOrder order = super.getById(id);
        order.setSettleAccountUsd(order.getSettleAccountUsd().subtract(settlementValueUsd).setScale(2,BigDecimal.ROUND_HALF_UP));
        order.setSettleAccountCny(order.getSettleAccountCny().subtract(settlementValueCny).setScale(2,BigDecimal.ROUND_HALF_UP));
        if(order.getSettleAccountCny().compareTo(BigDecimal.ZERO) == -1){
            throw new ServiceException(String.format("取消订单【%s】结汇人民币金额错误",order.getDocNo()));
        }
        // 结汇未完成
        order.setIsSettleAccount(Boolean.FALSE);
        // 未完成结汇票差金额零
        order.setDifferenceVal(BigDecimal.ZERO);
        // 实际采购总价
        order.setActualPurchaseCny(BigDecimal.ZERO);
        super.updateById(order);
    }

    /**
     * 通知关务归类
     * @param customer
     */
    public void classifyNotice(Customer customer) {
        //发送任务通知 通知关务归类
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.MATERIEL_EXP.getCode());
        taskNoticeContentDTO.setDocumentId("-1");
        taskNoticeContentDTO.setCustomerId(customer.getId());
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.MATERIEL_CLASSIFY_EXP.getCode());
        taskNoticeContentDTO.setContent(String.format("客户【%s】有新物料需要您归类，请立即处理。",customer.getName()));
        Map<String,Object> queryParamsMap = new LinkedHashMap<String,Object>(){{
            put("statusId", MaterielStatusEnum.WAIT_CLASSIFY.getCode());
        }};
        queryParamsMap.put("customerName",customer.getName());
        taskNoticeContentDTO.setQueryParamsMap(queryParamsMap);
        taskNoticeContentDTO.setIsExeRemove(Boolean.TRUE);
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    public Integer totalSingleQuantity(Date startDate, Date endDate) {
        endDate = DateUtils.parse(DateUtils.formatShort(endDate).concat(" 23:59:59"),"yyyy-MM-dd HH:mm:ss");
        return expOrderMapper.totalSingleQuantity(startDate,endDate);
    }

    @Override
    public BigDecimal totalAmountMoney(Date startDate, Date endDate) {
        endDate = DateUtils.parse(DateUtils.formatShort(endDate).concat(" 23:59:59"),"yyyy-MM-dd HH:mm:ss");
        return expOrderMapper.totalAmountMoney(startDate,endDate);
    }

    @Override
    public List<ImportExpOrderMemberVO> readDataFromMemberImport(MultipartFile file) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        List<ExpOrderMemberExcel> dataList;
        try {
            dataList = ExcelUtil.readExcel(file, ExpOrderMemberExcel.class, 1);
        } catch (Exception e) {
            log.error("读取导入的excel文件异常",e);
            throw new ServiceException("导入数据异常，请仔细检查您的导入文件是否符合模板要求");
        }
        if(CollectionUtils.isEmpty(dataList)) {
            throw new ServiceException("本次没有可以导入的数据");
        }
        //导入最大数目检查
        TsfPreconditions.checkArgument(dataList.size() <= uploadMaxmum,new ServiceException(String.format("您本次导入的数据超过%d条的上限了",uploadMaxmum)));
        //特殊符号处理
        dataList.stream().forEach(data->{
            data.setBrand(StringUtils.removeSpecialSymbol(data.getBrand()));
            data.setModel(StringUtils.removeSpecialSymbol(data.getModel()));
            data.setName(StringUtils.removeSpecialSymbol(data.getName()));
            data.setUnitName(StringUtils.removeSpecialSymbol(data.getUnitName()));
            data.setQuantity(StringUtils.removeSpecialSymbol(data.getQuantity()).replace(",",""));
            data.setTotalPrice(StringUtils.removeSpecialSymbol(data.getTotalPrice()).replace(",",""));
            data.setCartonNum(StringUtils.removeSpecialSymbol(data.getCartonNum()).replace(",",""));
            data.setNetWeight(StringUtils.removeSpecialSymbol(data.getNetWeight()).replace(",",""));
            data.setGrossWeight(StringUtils.removeSpecialSymbol(data.getGrossWeight()).replace(",",""));
            data.setGoodsCode(StringUtils.removeSpecialSymbol(data.getGoodsCode()));
            data.setSpec(StringUtils.removeSpecialSymbol(data.getSpec()));
        });
        List<ImportExpOrderMemberVO> imports = beanMapper.mapAsList(dataList,ImportExpOrderMemberVO.class);
        DecimalFormat format2 = new DecimalFormat("0.00");
        DecimalFormat format4 = new DecimalFormat("0.0000");
        imports.stream().forEach(importMember->{
            //数量和总价处理为2位小数点，毛重、净重保留4位小数
            if(StringUtils.isBigDecimal(importMember.getQuantity())) {
                importMember.setQuantity(importMember.getQuantity().contains(".") ? format2.format(new BigDecimal(importMember.getQuantity())) : importMember.getQuantity());
            } else {
                importMember.setQuantity(null);
            }
            if(StringUtils.isBigDecimal(importMember.getTotalPrice())) {
                importMember.setTotalPrice(importMember.getTotalPrice().contains(".") ? format2.format(new BigDecimal(importMember.getTotalPrice())) : importMember.getTotalPrice());
            } else {
                importMember.setTotalPrice(null);
            }
            if(StringUtils.isBigDecimal(importMember.getGrossWeight())) {
                importMember.setGrossWeight(importMember.getGrossWeight().contains(".") ? format4.format(new BigDecimal(importMember.getGrossWeight())) : importMember.getGrossWeight());
            } else {
                importMember.setGrossWeight("0.0000");
            }
            if(StringUtils.isBigDecimal(importMember.getNetWeight())) {
                importMember.setNetWeight(importMember.getNetWeight().contains(".") ? format4.format(new BigDecimal(importMember.getNetWeight())) : importMember.getNetWeight());
            } else {
                importMember.setNetWeight("0.0000");
            }
            //箱数
            if(!StringUtils.isInteger(importMember.getCartonNum())) {
                importMember.setCartonNum("0");
            }
            //单价后台处理
            if(StringUtils.isBigDecimal(importMember.getQuantity()) && StringUtils.isBigDecimal(importMember.getTotalPrice())) {
                BigDecimal quantity =  new BigDecimal(format2.format(new BigDecimal(importMember.getQuantity()))).multiply(new BigDecimal("100")).divide(new BigDecimal("100"));
                BigDecimal totalPrice = new BigDecimal(format2.format(new BigDecimal(importMember.getTotalPrice()))).multiply(new BigDecimal("100")).divide(new BigDecimal("100"));
                if(quantity.compareTo(BigDecimal.ZERO) == 1) {
                    importMember.setUnitPrice(totalPrice.divide(quantity,4,BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal("10000")).divide(new BigDecimal("10000")).toString());
                }
            } else {
                importMember.setUnitPrice("0.0000");
            }
        });
        stopwatch.stop();
        log.info("本次成功导入{}条数据，耗时：{}秒",dataList.size(),stopwatch.elapsed(TimeUnit.SECONDS));
        return imports;
    }

    @Override
    public List<OrderCrossBorderStatusVO> getTransportNoByOrderNo(List<String> orderNos) {
        return expOrderMapper.getTransportNoByOrderNo(orderNos);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int batchUpdateStatus(String statusId, List<String> orderNos) {
        if(CollUtil.isEmpty(orderNos)) {return 0;}
        return expOrderMapper.batchUpdateStatus(statusId,orderNos);
    }

    @Override
    public ExpOrderEntrustVO getPrintEntrustData(Long id) {
        ExpOrderVO expOrderVO = expOrderMapper.detail(id);
        ValidatorUtils.isTrue(Objects.nonNull(expOrderVO),()->new ServiceException("订单不存在"));
        ExpOrderLogistics expOrderLogistics = expOrderLogisticsService.findByOrderId(id);
        ValidatorUtils.isTrue(Objects.nonNull(expOrderLogistics),()->new ServiceException("订单不存在"));
        Declaration declaration = declarationService.findByOrderId(id);

        ExpOrderEntrustVO expOrderEntrustVO = new ExpOrderEntrustVO();
        expOrderEntrustVO.setOrderNo(expOrderVO.getDocNo());
        expOrderEntrustVO.setOrderDate(expOrderVO.getOrderDate());
        expOrderEntrustVO.setCustomerName(expOrderVO.getCustomerName());
        expOrderEntrustVO.setSubjectName(expOrderVO.getSubjectName());
        expOrderEntrustVO.setSupplierName(expOrderVO.getSupplierName());
        ValidatorUtils.isTrueCall(Objects.nonNull(declaration),()->{
            expOrderEntrustVO.setIePortName(declaration.getIePortName());
        });

        expOrderEntrustVO.setCurrencyName(expOrderVO.getCurrencyName());
        expOrderEntrustVO.setDeliveryModeName(expOrderLogistics.getDeliveryModeName());
        expOrderEntrustVO.setTakeLinkPerson(expOrderLogistics.getTakeLinkPerson());
        expOrderEntrustVO.setTakeAddress(expOrderLogistics.getTakeAddress());
        expOrderEntrustVO.setTakeLinkTel(expOrderLogistics.getTakeLinkTel());
        expOrderEntrustVO.setIsCharterCar(expOrderLogistics.getIsCharterCar());
        expOrderEntrustVO.setDeliveryDestination(expOrderLogistics.getDeliveryDestination());

        DeliveryDestinationEnum deliveryDestinationEnum = DeliveryDestinationEnum.of(expOrderLogistics.getDeliveryDestination());
        expOrderEntrustVO.setDeliveryDestinationName(Optional.ofNullable(deliveryDestinationEnum).map(DeliveryDestinationEnum::getName).orElse(""));
        expOrderEntrustVO.setTakeLinkCompany(expOrderLogistics.getTakeLinkCompany());
        expOrderEntrustVO.setTakeAddress(expOrderLogistics.getTakeAddress());
        expOrderEntrustVO.setTakeLinkPerson(expOrderLogistics.getTakeLinkPerson());
        expOrderEntrustVO.setTakeLinkTel(expOrderLogistics.getTakeLinkTel());

        expOrderEntrustVO.setReceivingModeMemo(expOrderLogistics.getReceivingModeMemo());

        expOrderEntrustVO.setMemo(expOrderVO.getMemo());

        List<ExpOrderMember> expOrderMembers = expOrderMemberService.getByOrderId(id);

        List<ExpOrderEntrustVO.InnerExpOrderMember> members = Lists.newArrayListWithExpectedSize(expOrderMembers.size());
        expOrderMembers.stream().forEach(member->{
            ExpOrderEntrustVO.InnerExpOrderMember innerMember = beanMapper.map(member,ExpOrderEntrustVO.InnerExpOrderMember.class);
            members.add(innerMember);
        });
        expOrderEntrustVO.setMembers(members);
        return expOrderEntrustVO;

    }

    @Override
    public BigDecimal summaryOrderAmount(ExpOrderQTO qto) {
        Map<String,Object> params = beanMapper.map(qto,Map.class);
        return expOrderMapper.summaryOrderAmount(params);
    }

    @Override
    public List<String> findClientNoByDocNos(List<String> docNo) {
        if(CollectionUtil.isEmpty(docNo)){
            return new ArrayList<>();
        }
        return expOrderMapper.findClientNoByDocNos(docNo);
    }
}
